/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package github.hellocsl.layoutmanager.gallery;

import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.ListContainer;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.Text;
import ohos.agp.components.AttrSet;
import ohos.agp.utils.Color;
import ohos.app.Context;

/**
 * 画廊列表效果
 *
 * @since 2021-08-09
 */
public class GalleryListContainer extends ListContainer implements Component.ScrolledListener,
        ComponentContainer.ArrangeListener {
    private static final int CONSTANT_1 = 1;
    private static final int CONSTANT_2 = 2;
    private static final int CONSTANT_30 = 30;
    private static final float CONSTANT_05F = 0.5f;
    private static final float CONSTANT_03F = 0.3f;
    private static final float CONSTANT_1F = 1f;
    private static final float CONSTANT_2F = 2f;

    // 默认动画时长
    private static final int DEFAULT_ANIMATOR_DURATION = 300;

    // 是否设置开始和结束的偏移量
    private boolean isSetOffset = false;

    // 是否自然滑动
    private boolean isScrollNormal = false;

    // item动画切换
    private ItemTransformer itemTransformer;

    // item选中回调
    private ItemSelectedCallback itemSelectedCallback;

    // 滑动到中心的动画
    private final AnimatorValue animatorValue = new AnimatorValue();

    // 滑动到中心的动画时长
    private int animatorDuration = DEFAULT_ANIMATOR_DURATION;

    // 是调用scrollToCenter方法
    private boolean isScrollToCenter = false;

    // 是调用scrollToCenter方法的position
    private int scrollToCenterPosition = 0;

    // 选中的position
    private int currentSelectPosition = 0;

    // item宽度
    private int itemWidth = 0;

    // item高度
    private int itemHeight = 0;

    /**
     * 构造方法
     *
     * @param context context
     */
    public GalleryListContainer(Context context) {
        this(context, null);
    }

    /**
     * 构造方法
     *
     * @param context context
     * @param attrSet attrSet
     */
    public GalleryListContainer(Context context, AttrSet attrSet) {
        this(context, attrSet, null);
    }

    /**
     * attrSet
     *
     * @param context context
     * @param attrSet attrSet
     * @param styleName styleName
     */
    public GalleryListContainer(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);

        // 排列子组件监听
        setArrangeListener(this);

        // 滑动监听
        setScrolledListener(this);
    }

    /**
     * 设置滑动到中心的动画时长
     *
     * @param animatorDuration 动画时长
     */
    public void setAnimatorDuration(int animatorDuration) {
        this.animatorDuration = animatorDuration;
    }

    /**
     * 设置item动画切换
     *
     * @param itemTransformer 动画切换
     */
    public void setItemTransformer(ItemTransformer itemTransformer) {
        this.itemTransformer = itemTransformer;
    }

    /**
     * 设置item选中回调
     *
     * @param itemSelectedCallback item选中回调
     */
    public void setItemSelectedCallback(ItemSelectedCallback itemSelectedCallback) {
        this.itemSelectedCallback = itemSelectedCallback;
    }

    @Override
    public boolean onArrange(int left, int top, int width, int height) {
        if (!isSetOffset) {
            for (int index = 0; index < getChildCount(); index++) {
                Component item = getComponentAt(index);
                if (item != null) {
                    itemWidth = item.getEstimatedWidth();
                    itemHeight = item.getEstimatedHeight();

                    // 计算开始和结束的偏移量，保证第一个居中显示
                    int offset = (int) calculateStartAndEndOffset(item);
                    setContentOffSet(offset, offset);
                    isSetOffset = true;
                    break;
                }
            }
        }
        return false;
    }

    /**
     * 计算开始和结束偏移量，根据方向判断，左右或上下
     *
     * @param item item
     * @return 偏移量
     */
    private float calculateStartAndEndOffset(Component item) {
        float offset;
        if (getOrientation() == Component.HORIZONTAL) {
            offset = getPivotX() - item.getEstimatedWidth()
                    / CONSTANT_2F - getPaddingLeft() - item.getMarginLeft();
        } else {
            offset = getPivotY() - item.getEstimatedHeight()
                    / CONSTANT_2F - getPaddingTop() - item.getMarginTop();
        }
        return offset;
    }

    @Override
    public void scrollToCenter(int position) {
        int offsetPosition;
        if (getOrientation() == HORIZONTAL) {
            offsetPosition = (int) (getPivotX() / itemWidth);
        } else {
            offsetPosition = (int) (getPivotY() / itemHeight);
        }

        if (currentSelectPosition == position) {
            return;
        }

        if (position - offsetPosition > 0) {
            super.scrollToCenter(position - offsetPosition);
        } else {
            isScrollToCenter = true;
            scrollToCenterPosition = position;
            super.scrollToCenter(0);
        }
    }

    @Override
    public void onContentScrolled(Component component, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
        scrolledTransformItem();
    }

    /**
     * 滑动item动画切换
     */
    private void scrolledTransformItem() {
        for (int index = 0; index < getChildCount(); index++) {
            Component item = getComponentAt(index);
            if (item != null) {
                // item动画切换系数
                float factor = calculateTransformItemFactor(item);
                if (itemTransformer != null) {
                    itemTransformer.transformItem(getOrientation(), item, factor);
                }

                // 更改中间文字颜色字体
                if (item instanceof Text) {
                    if (factor > -CONSTANT_05F && factor < CONSTANT_05F) {
                        ((Text) item).setTextColor(new Color(Color.getIntColor("#FFC107")));
                    } else {
                        ((Text) item).setTextColor(new Color(Color.getIntColor("#000000")));
                    }
                }

                // 到达中心更新item选中回调
                if (factor > -CONSTANT_03F && factor < CONSTANT_03F) {
                    currentSelectPosition = index;
                    if (itemSelectedCallback != null) {
                        itemSelectedCallback.itemSelected(item, index);
                    }
                }
            }
        }
    }

    /**
     * 计算item动画切换系数
     *
     * @param item item
     * @return item动画切换系数
     */
    private float calculateTransformItemFactor(Component item) {
        // item轴心距离列表中心
        float distance;
        float factor;
        if (getOrientation() == Component.HORIZONTAL) {
            distance = getPaddingLeft() + item.getContentPositionX() + item.getWidth() / CONSTANT_2F - getPivotX();
            factor = Math.max(-CONSTANT_1F, Math.min(CONSTANT_1F, distance / item.getWidth()));
        } else {
            distance = getPaddingTop() + item.getContentPositionY() + item.getHeight() / CONSTANT_2F - getPivotY();
            factor = Math.max(-CONSTANT_1F, Math.min(CONSTANT_1F, distance / item.getHeight()));
        }
        return factor;
    }

    @Override
    public void scrolledStageUpdate(Component component, int newStage) {
        switch (newStage) {
            case SCROLL_IDLE_STAGE:
                if (isScrollToCenter) {
                    isScrollToCenter = false;
                    animatorDuration = CONSTANT_30;
                    scrollByCenter(getComponentAt(scrollToCenterPosition));
                } else {
                    if (isScrollNormal) {
                        isScrollNormal = false;
                        scrollByCenter(findCenterItem());
                    }
                }
                break;
            case SCROLL_NORMAL_STAGE:
                isScrollNormal = true;
                break;
            case SCROLL_AUTO_STAGE:
                isScrollNormal = true;
                break;
            default:
                break;
        }
    }

    /**
     * 查找靠近中心位置的item
     *
     * @return item
     */
    private Component findCenterItem() {
        // 靠近中心位置的item
        Component centerItem = null;

        // item轴心距离列表中心
        float distance;
        float minDistance = -CONSTANT_1;
        for (int index = 0; index < getChildCount(); index++) {
            Component item = getComponentAt(index);
            if (item != null) {
                if (getOrientation() == Component.HORIZONTAL) {
                    distance = getPaddingLeft() + item.getContentPositionX()
                            + item.getWidth() / CONSTANT_2F - getPivotX();
                } else {
                    distance = getPaddingTop() + item.getContentPositionY()
                            + item.getHeight() / CONSTANT_2F - getPivotY();
                }
                distance = Math.abs(distance);
                if (minDistance == -CONSTANT_1) {
                    minDistance = distance;
                }
                if (distance <= minDistance) {
                    minDistance = distance;
                    centerItem = item;
                }
            }
        }
        return centerItem;
    }

    /**
     * 滑动到中心位置
     *
     * @param itemPosition itemPosition
     */
    public void clickedScrollByCenter(int itemPosition) {
        scrollByCenter(getComponentAt(itemPosition));
    }

    /**
     * 滑动到中心位置
     *
     * @param centerItem centerItem
     */
    public void scrollByCenter(Component centerItem) {
        if (centerItem == null) {
            return;
        }

        // 列表中心点
        float listPivot;

        // item的轴心坐标
        float centerChildPivot;

        // 计算偏移量
        float offset;

        // 计算动画时长
        long duration;

        if (getOrientation() == Component.HORIZONTAL) {
            listPivot = getPivotX();
            centerChildPivot = getPaddingLeft() + centerItem.getContentPositionX()
                    + centerItem.getWidth() / CONSTANT_2F;
            offset = centerChildPivot - listPivot;
            duration = (long) (animatorDuration * Math.abs(offset) / centerItem.getWidth() / CONSTANT_2F);
        } else {
            listPivot = getPivotY();
            centerChildPivot = getPaddingTop() + centerItem.getContentPositionY()
                    + centerItem.getHeight() / CONSTANT_2F;
            offset = centerChildPivot - listPivot;
            duration = (long) (animatorDuration * Math.abs(offset) / centerItem.getHeight() / CONSTANT_2F);
        }

        animatorValue.stop();
        animatorValue.setDuration(duration);
        animatorValue.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            float beforeOffset = 0f;
            int totalOffsetInt = 0;

            @Override
            public void onUpdate(AnimatorValue ani, float value) {
                // 滑动偏移量
                int scrollByOffset = (int) (value * offset - beforeOffset);
                if (getOrientation() == Component.HORIZONTAL) {
                    scrollBy(scrollByOffset, 0);
                } else {
                    scrollBy(0, scrollByOffset);
                }

                // 滑动偏移量为int，记录实际总int偏移量，为了计算精度误差
                totalOffsetInt += (int) (value * offset - beforeOffset);

                // 记录之前走的偏移量
                beforeOffset = value * offset;

                // 动画结束之后，滑动相差的精度
                if (value == 1.0f) {
                    if (getOrientation() == Component.HORIZONTAL) {
                        scrollBy((int) (offset - totalOffsetInt), 0);
                    } else {
                        scrollBy(0, (int) (offset - totalOffsetInt));
                    }
                    animatorDuration = DEFAULT_ANIMATOR_DURATION;
                }
            }
        });
        animatorValue.start();
    }

    /**
     * item动画切换
     *
     * @since 2021-08-09
     */
    public interface ItemTransformer {
        /**
         * item动画切换
         *
         * @param orientation orientation
         * @param item item
         * @param factor factor
         */
        void transformItem(int orientation, Component item, float factor);
    }

    /**
     * item选中回调
     *
     * @since 2021-08-09
     */
    public interface ItemSelectedCallback {
        /**
         * item选中回调
         *
         * @param item item
         * @param position position
         */
        void itemSelected(Component item, int position);
    }
}
